package com.gr.jron.webapp.action;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

import javax.servlet.http.HttpServletRequest;

import org.acegisecurity.captcha.CaptchaEntryPoint;
import org.acegisecurity.captcha.CaptchaSecurityContext;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;

/**
 * Created by Jronrun.
 * User: jron
 * Date: 2007-11-3
 * Time: 13:07:58
 * It's jron's dream to cqiu
 * <p/>
 * Action class to validate and properly forward users to their originally
 * requested, pre-captcha URL. The pre-captcha url is maintained by get/set
 * methods using hidden form fields and is decoded as needed when the CAPTCHA is
 * successful.
 */
public final class CaptchaAction extends BaseAction {

    private static final long serialVersionUID = 3832626162173359411L;

    private CaptchaEntryPoint captchaEntryPoint;
    private String original_request_method = null;
    private String original_requestUrl = null;
    private String original_request_parameters = null;
    private String j_captcha_response = null;

    /**
     * Default Action method.
     *
     * @return
     */
    public String execute() {
        // test the security context
        SecurityContext sContext = SecurityContextHolder.getContext();

        if (sContext instanceof CaptchaSecurityContext) {
            CaptchaSecurityContext captchaContext = (CaptchaSecurityContext) sContext;

            if (log.isDebugEnabled()) {
                log.debug("j_captcha_response: " + j_captcha_response
                        + " requestCnt: " + captchaContext.getHumanRestrictedResourcesRequestsCount()
                        + " lastPassedDateMillis: " + captchaContext.getLastPassedCaptchaDateInMillis());
            }

            if (captchaContext.isHuman()) {
                log.debug("captcha found a human");

                if (captchaEntryPoint.isIncludeOriginalRequest()) {
                    log.debug("using original request to redirect human");
                    String redirectUrl = getOriginalRequestUrl(getRequest());

                    if (log.isDebugEnabled()) {
                        // WARNING: this could have passwords in it, so only use
                        // for debugging
                        log.debug("redirecting human from captcha to: " + redirectUrl);
                    } else {
                        log.info("found human using captcha");
                    }

                    if (redirectUrl != null) {
                        try {
                            // this is the primary exit point
                            getResponse().sendRedirect(redirectUrl);
                            return NONE; // redirect will handle it
                        } catch (IOException ioe) {
                            log.warn("couldn't redirect user to " + redirectUrl);
                        }
                    } else {
                        return INPUT; // need some input
                    }
                }

            } else {
                log.warn("captcha failed, must retest");
            }
        } else {
            // usually get here when httpSessionContextIntegrationFilter is
            // misconfigured
            log.fatal("possible misconfiguration securityContext is not CaptchaSecurityContext");
        }

        // default response
        return ERROR;
    }

    /**
     * Allow the bean to be configured via Spring.
     *
     * @param captchaEntryPoint
     */
    public void setCaptchaEntryPoint(CaptchaEntryPoint captchaEntryPoint) {
        this.captchaEntryPoint = captchaEntryPoint;
    }

    /**
     * This method should really be a static convenience method in the
     * org.acegisecurity.captcha.CaptchaEntryPoint class. Instead, it's
     * implemented here for now.
     *
     * @param origReqUrl
     * @param origReqParams
     * @param paramSep
     * @param paramNVSep
     * @param clientEncoding
     * @return
     */
    private String getOriginalRequestUrl(HttpServletRequest request) {

        String origReqUrl = request.getParameter(captchaEntryPoint.getOriginalRequestUrlParameterName());

        if (origReqUrl == null || origReqUrl.length() == 0) {
            log.warn("url was null, send to the default location");
            return null;
        }

        String origReqMethod = request.getParameter(captchaEntryPoint.getOriginalRequestMethodParameterName());

        // TODO: handle POST for a redirect?
        // decode url according to charset
        String origReqParams = null;
        try {
            origReqUrl = URLDecoder.decode(origReqUrl, captchaEntryPoint.getUrlEncodingCharset());

            // user.email__;;button.register__Signup;;user.username__bridget;;user.passwordHint__bridget;;user.firstName__;;user.phoneNumber__;;user.address.province__;;user.address.postalCode__;;user.address.city__;;user.address.country__;;user.address.address__;;user.password__bridget;;user.website__;;user.lastName__;;user.confirmPassword__bridget
            origReqParams = request.getParameter(captchaEntryPoint.getOriginalRequestParametersParameterName());

            // decode params, just as they were encoded
            origReqParams = URLDecoder.decode(origReqParams, captchaEntryPoint.getUrlEncodingCharset());

            String paramNVSep = captchaEntryPoint.getOriginalRequestParametersNameValueSeparator();

            String paramSep = captchaEntryPoint.getOriginalRequestParametersSeparator();

            // log if needed
            if (log.isDebugEnabled()) {
                log.debug("origReqUrl: " + origReqUrl);
                log.debug("origReqMethod: " + origReqMethod);
                log.debug("origReqParams: " + origReqParams);
                log.debug("paramNVSep: " + paramNVSep);
                log.debug("paramSep: " + paramSep);
            }

            // default encoding for the redirect based on the client
            String clientEncoding = request.getCharacterEncoding();
            if (clientEncoding == null || clientEncoding.length() == 0) {
                clientEncoding = "UTF-8";
            }

            // get the redirect
            return getOriginalRequestUrl(origReqUrl, origReqParams, paramSep, paramNVSep, clientEncoding);

        } catch (UnsupportedEncodingException uee) {
            log.warn("couldn't decode: \"" + origReqUrl + "\" using charset: "
                    + captchaEntryPoint.getUrlEncodingCharset());
        } catch (IllegalArgumentException iae) {
            log.warn("couldn't decode some parameter, reverting to url with no parameters");
            return origReqUrl;
        }
        return null;
    }

    /**
     * Build the redirect url by parsing the captchaEntryPoint parameters. Note:
     * the captchaEntryPoint doesn't seem to encode the request parameters using
     * URLEncode, but the documentation says it should.
     *
     * @param origReqUrl
     * @param origReqParams
     * @param paramSep
     * @param paramNVSep
     * @param clientEncoding
     * @return
     * @throws UnsupportedEncodingException
     */
    private String getOriginalRequestUrl(String origReqUrl, String origReqParams, String paramSep,
                                         String paramNVSep, String clientEncoding) throws UnsupportedEncodingException {
        // build the get URL
        StringBuffer redirectUrl = new StringBuffer(origReqUrl);

        // rebuild the request string by parsing the query
        // params
        if (origReqParams != null && origReqParams.length() > 0) {
            if (redirectUrl.indexOf("?") >= 0) redirectUrl.append("&");
            else redirectUrl.append("?");

            int offset = 0;
            while (origReqParams.indexOf(paramSep, offset) != -1) {
                String nv = origReqParams.substring(offset, origReqParams.indexOf(paramSep, offset));

                String name = nv.substring(0, nv.indexOf(paramNVSep));
                String value = nv.substring(name.length() + paramNVSep.length());
                // append and encode
                redirectUrl.append(URLEncoder.encode(name, clientEncoding));
                redirectUrl.append("=");
                redirectUrl.append(URLEncoder.encode(value, clientEncoding));

                offset += nv.length() + paramSep.length();

                // next param?
                if (origReqParams.indexOf(paramSep, offset) >= 0) redirectUrl.append("&");

            }
        }
        return redirectUrl.toString();
    }

    /**
     * The captcha form will use the j_captcha_response form field to validate
     * the user.
     *
     * @param j_captcha_response
     */
    public void setJ_captcha_response(String j_captcha_response) {
        this.j_captcha_response = j_captcha_response;
    }

    /**
     * Remaining methods help maintain the original URL between unsuccessful
     * CAPTCHAs
     */

    public String getOriginal_request_method() {
        return this.original_request_method;
    }

    public void setOriginal_request_method(String original_request_method) {
        this.original_request_method = original_request_method;
    }

    public String getOriginal_request_parameters() {
        return this.original_request_parameters;
    }

    public void setOriginal_request_parameters(
            String original_request_parameters) {
        this.original_request_parameters = original_request_parameters;
    }

    public String getOriginal_requestUrl() {
        return this.original_requestUrl;
    }

    public void setOriginal_requestUrl(String original_requestUrl) {
        this.original_requestUrl = original_requestUrl;
    }
}
